package cn.smile.config.spring.shiro;

import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.cache.MemoryConstrainedCacheManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.apache.shiro.web.filter.mgt.DefaultFilter;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.mgt.WebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;

import java.util.LinkedHashMap;
import java.util.Properties;

/**
 * Created with IntelliJ IDEA.
 *
 * @Author: MaoSuyu
 * @User：John
 * @Date: 2019/7/2
 * @Time: 15:16
 * @Description: No Description
 */
@Configuration
public class ShiroConfiguration {

    /**
     * Realm在验证用户身份的时候，要进行密码匹配
     * 最简单的情况就是明文直接匹配，然后就是加密匹配，这里的匹配工作则就是交给CredentialsMatcher来完成
     * 支持任意数量的方案，包括纯文本比较、散列比较和其他方法。除非该方法重写，否则默认值为
     * @return
     */
    @Bean
    public HashedCredentialsMatcher credentialsMatcher() {
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        //加密算法
        credentialsMatcher.setHashAlgorithmName("Md5");
        //迭代次数
        credentialsMatcher.setHashIterations(2048);
        return credentialsMatcher;
    }


    /**
     * 自定义的Realm
     * @param matcher
     * @return
     */
    @Bean
    //可选
    @DependsOn("lifecycleBeanPostProcessor")
    public WebShiroRealm webShiroRealm(@Autowired HashedCredentialsMatcher matcher) {
        WebShiroRealm webShiroRealm = new WebShiroRealm();
        //这边可以选择是否将认证的缓存到内存中，现在有了这句代码就将认证信息缓存的内存中了，这里选择注释，因为使用了redis
        //webShiroRealm.setCacheManager(new MemoryConstrainedCacheManager());
        //最简单的情况就是明文直接匹配，然后就是加密匹配，这里的匹配工作则就是交给CredentialsMatcher来完成
        webShiroRealm.setCredentialsMatcher(matcher);
        return webShiroRealm;
    }


    /**
     * 定义安全管理器securityManager,注入自定义的realm
     * @return
     */
    @Bean
    public SecurityManager securityManager(@Autowired WebShiroRealm webShiroRealm) {
        //这个DefaultWebSecurityManager构造函数,会对Subject，realm等进行基本的参数注入
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        //往SecurityManager中注入Realm，代替原本的默认配置
        manager.setRealm(webShiroRealm);
        return manager;
    }


    /**
     * 对Shiro注解进行支持
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Autowired SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        //绑定安全管理器
        advisor.setSecurityManager(securityManager);
        return advisor;
    }


    /**
     *  @Qualifier("XXX") Spring的Bean注入配置注解，该注解指定注入的Bean的名称，
     *  Spring框架使用byName方式寻找合格的bean，这样就消除了byType方式产生的歧义。
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(@Autowired SecurityManager manager) {
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        System.out.println(bean);
        bean.setSecurityManager(manager);
        //提供登录到url
        bean.setLoginUrl("/login");
        //提供登陆成功的url
        bean.setSuccessUrl("/index");
        //没有权限时跳转的页面，必须配置SimpleMappingExceptionResolver的bean否则会报Subject does not have permission [权限名]的错误
        //bean.setUnauthorizedUrl("/unauthorized");
        /**
         * 可以看DefaultFilter,这是一个枚举类，定义了很多的拦截器authc,anon等分别有对应的拦截器
         */
        //配置访问权限
        LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        //代表着前面的url路径，用后面指定的拦截器进行拦截
        filterChainDefinitionMap.put("/index", "authc");
        filterChainDefinitionMap.put("/login", "anon");
        filterChainDefinitionMap.put("/login.do", "anon");
        //退出
        filterChainDefinitionMap.put("/out", "logout");
        //所有的druid请求，不需要拦截，anon对应的拦截器不会进行拦截
        filterChainDefinitionMap.put("/druid/**", "anon");
        //所有的路径都拦截，被UserFilter拦截，这里会判断用户有没有登陆
        filterChainDefinitionMap.put("/**", "user");
        //设置一个拦截器链
        bean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return bean;
    }


    /**
     * Spring的一个bean , 由Advisor决定对哪些类的方法进行AOP代理
     * @return
     */
    @Bean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
        creator.setProxyTargetClass(true);
        return creator;
    }


    /**
     * lifecycleBeanPostProcessor是负责生命周期的 , 初始化和销毁的类
     * (可选)
     * @return
     */
    @Bean("lifecycleBeanPostProcessor")
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }


    /**
     * 由于springmvc会对异常自动进行处理，当用户没有权限操作时，页面会报错而不会跳转至无权限页面
     * 配置该bean会解决该问题
     * @return
     */
    @Bean
    public SimpleMappingExceptionResolver simpleMappingExceptionResolver(){
        SimpleMappingExceptionResolver simpleMappingExceptionResolver=new SimpleMappingExceptionResolver();
        Properties properties = new Properties();
        //异常类的全限定名和发生该异常后需要跳转到的页面
        properties.put("org.apache.shiro.authz.UnauthorizedException","redirect:/unauthorized");
        simpleMappingExceptionResolver.setExceptionMappings(properties);
        return simpleMappingExceptionResolver;
    }

}
